#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <pthread.h>
#include <semaphore.h>
#include <math.h>
#include <time.h>
#include <sys/syscall.h>

#define N 2 //constant representing # of students
#define M 3 //constant representing # of books
#define T 2  //constant for the amount of time
#define TRUE 1
#define FALSE 0

#define IA 16807
#define IM 2147483647
#define AM (1.0/IM) 
#define IQ 127773 
#define IR 2836
#define NTAB 32
#define NDIV (1+(IM-1)/NTAB)
#define EPS 1.2e-7
#define RNMX (1.0 - EPS) 

/* GLOBAL VARIABLES */
typedef int boolean;


// Define book structure
typedef struct {
        int id;
        int num_pages;
        boolean selected;
        boolean returned;
} booktype;

// Declare semaphores and total_pages global variable
sem_t sem_people, sem_books, sem_check1, sem_check2;
int total_pages;
booktype books[M];
int numPeople;

// Functions used 
void *student_thread(void *param);
void meet_at_the_library();
booktype* pick_a_book();
void read_book(booktype* book);
boolean share_impressions(booktype* book);
void return_book(booktype* book);
void initializeBooks(booktype* book);
int normalRandom(long *idum);
float exponentialRandom(long *idum);

long someNum = .05;

int main()
{
        int i; //used for counter when creating threads
        int j; //used for counter when joining threads
        int k; //used to instantiate books linked list
           
        //initializing total pages read and semaphores
        total_pages = 0;
        numPeople = 0;
        sem_init(&sem_people,0,N);
        sem_init(&sem_books,0,M);
        sem_init(&sem_check1,0,1);
        sem_init(&sem_check2,0,1);
                
        /* Array */
        // This makes M number of books, stores them into an array, and intializes all values
        for (k =0; k < M; k++)
        {
                books[k].id = k;
                books[k].num_pages = normalRandom(&someNum);
                books[k].selected=FALSE;
                books[k].returned=FALSE;
        }
                
        //creating N student threads
        pthread_t tid[N];
        pthread_attr_t pt_attribs;
                
        for (i=0; i<N; i++)
        {
                pthread_attr_init(&pt_attribs);
                pthread_create(&(tid[i]), &pt_attribs, student_thread, NULL);
        }

        for (j=0; j<N; j++)
                pthread_join(tid[j], NULL);

        return 0;
}

//function for threads provided by Dr. Varsamopoulos
void *student_thread(void *param)
{
        booktype* book = (booktype*) malloc(sizeof(booktype));
        boolean done = FALSE;
        meet_at_the_library();
        do {
                book = pick_a_book();
                read_book(book);
                done = share_impressions(book);
                return_book(book);
        }while(!done);
        pthread_exit(0);
}

//This function returns after all student threads enter it
void meet_at_the_library()
{

        // Time information
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;
        boolean everyone_here = FALSE;
        int value;              

        // Prints out current time each thread enters
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering meet_at_the_library()\n",buffer,(long int) syscall(SYS_gettid));
       
        sem_wait(&sem_check1);
        numPeople++;
        sem_post(&sem_check1);

        while(numPeople < N)
        {
                usleep(1);
        }
                
        usleep(2);

        numPeople = 0;

        // Prints out time each thread exits
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting meet_at_the_library() ",buffer,(long int) syscall(SYS_gettid));

        // Prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);
}

booktype* pick_a_book()
{
        // Time infomation
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;
                
        // Variables associated with selecting a book
        int i,j;
        int numOfAvailableBooks;
        booktype *book = (booktype *) malloc(sizeof(booktype));
        numPeople = 0;
                
        //prints out current time & function entered
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering pick_a_book()\n",buffer,(long int) syscall(SYS_gettid));       
                
        sem_wait(&sem_check1);
                
        numOfAvailableBooks = 0;
        for(i=0; i<M; i++)
        {
                if(books[i].selected==FALSE && books[i].returned==FALSE)
                        numOfAvailableBooks++;
        }

        srand(time(NULL));

        if (numOfAvailableBooks>0)
        {
                do {
                        j = rand()%M;
                }while(books[j].selected==TRUE || books[j].returned==TRUE);

                books[j].selected = TRUE;
                book = &books[j];
        }
        else
        {
                book = NULL;
        }
                
        sem_post(&sem_check1);

        //prints out time & function exiting
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting pick_a_book() ",buffer,(long int) syscall(SYS_gettid));

        //prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);

        //Return book selected by this specific student thread
        return book;
}

void read_book(booktype* book)
{
        // Time information
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;

        // Prints out current time & function entered
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering read_book()\n",buffer,(long int) syscall(SYS_gettid));
                
        /* Each student thread can be in read_book at the same time, 
        and when each are done reading their book they will leave and 
        go to the share_impressions function */
   
        long daPages = T*(books->num_pages);
   
        int sleepTime = (int) T*(books->num_pages)*exponentialRandom(&daPages);
   
        printf("time to sleep = %d\n",sleepTime);
   
        if (book != NULL)
        {
                usleep(sleepTime);
        }
                
        // Prints out time & function exiting
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting read_book() ",buffer,(long int) syscall(SYS_gettid));

        // Prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);

}

boolean share_impressions(booktype* book)
{
        // Time information
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;
        boolean everyone_here = FALSE;
                
        // Variable used with values of sem_people
        int value;
        int availableBooks;
        int i;
        int numPeople2 = 0;

        // Prints out current time & function entered
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering share_impressions()\n",buffer,(long int) syscall(SYS_gettid));

        // Waits for all of the people to arrive and totals the amount of pages read
        //sem_wait(&sem_people);
        sem_wait(&sem_check2);
        if(book != NULL)
        {
                total_pages += book->num_pages;
        }
        numPeople++;
        //sem_getvalue(&sem_people,&value);
        //if(value <= 0)
        //      everyone_here = TRUE;
        sem_post(&sem_check2);
		sleep(1);
        while (numPeople < N)
        {
                usleep(1);
        }

        long daPages = T*total_pages;
        int timeToSleep = (int) T*total_pages*exponentialRandom(&daPages);
        printf("time to sleep = %d\n",timeToSleep);

        usleep(timeToSleep);
		
		sleep(1);

        //sem_post(&sem_people);
        total_pages=0;
        numPeople = 0;
                
        // Prints out time & function exiting
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting share_impressions() ",buffer,(long int) syscall(SYS_gettid));

        // Prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);

        sem_wait(&sem_check2);
        availableBooks = 0;
        for(i=0;i<M;i++)
        {
                if(books[i].selected == TRUE)
                        availableBooks++;
        }
        if(availableBooks >= M)
        {
                sem_post(&sem_check2);
                return TRUE;
        }
        else
        {
                sem_post(&sem_check2);
                return FALSE;
        }
        return FALSE;
}

void return_book(booktype* book)
{
        // Time information
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;
        int i;
                
        // Prints out current time & function entered
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering return_book()\n",buffer,(long int) syscall(SYS_gettid));
                
        book->returned = TRUE;      
 
        // Prints out time & function exiting
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting return_book() ",buffer,(long int) syscall(SYS_gettid));

        // Prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);

}

//Needed to be implemented in the exponentialRandom function and
//the normalRandom function
float ran1(long *idum)
{
  int j;
  long k;
  static long iy = 0;
  static long iv[NTAB];
  float temp;
  
  if (*idum <= 0 || *idum <= !iy)
  {
    if (-(*idum) < 1)
    {
       *idum = 1;
                }
                else
                {
       *idum = -(*idum);
                }
     for (j = NTAB +7; j>=0; j--)
     {
       k = (*idum)/IQ;
       *idum = IA*(*idum-k*IQ)-IR*k;
       if (*idum < 0) *idum += IM;
       if (j < NTAB) iv[j] = *idum;
     }
     iy = iv[0];
   }
   k=(*idum)/IQ;
   *idum=IA*(*idum-k*IQ)-IR*k;

   if (*idum < 0){
   *idum += IM;
   }
   
   j=iy/NDIV;
   iy=iv[j];
   iv[j] = *idum;
   
        if ((temp=AM*iy) > RNMX)
  {
    return RNMX;
   }
   else {
    return temp;
   }
}

//Normal distribution random numbers
//To get the proper random numbers though,
int normalRandom(long *idum)
{
  float ran1(long *idum);
  static int iset = 0;
  static float gset;
  float fac, rsq, v1, v2;
  float v2Fac;
  int thingToReturn;
  int rounded;
  int mean = 400;

  if (*idum < 0) iset = 0;
  if (iset == 0) {
    do {
      v1 = 2.0*ran1(idum) - 1.0;
      v2 = 2.0*ran1(idum) - 1.0;
      rsq = v1*v1 + v2*v2;
    }
    while (rsq >= 1.0 || rsq == 0.0);
    fac = sqrt(-2.0 * log(rsq)/rsq);
    gset = v1*fac;
    iset = 1;
    v2Fac = v2*fac;
    thingToReturn = (400 + v2Fac*200);
    rounded = round(thingToReturn);
    if (thingToReturn < 10)
    {
      return mean;
    }
    else {
      return rounded;
    }
  }
  else {
    iset = 0;
    return gset;
  }
}


float exponentialRandom(long *idum)
{
  float ran1(long *idum);
  float dum;
  
  do  
    dum = ran1(idum); 
  while (dum == 0.0);

  return -log(dum);
}
